# 鸢尾花分类
# 鸢尾花数据集是一个经典的机器学习数据集，常用于分类任务的示例和教学。
# sklearn 是 scikit-learn 的简写，一个流行的机器学习库。
# datasets 是 scikit-learn 中的一个模块，提供了多种内置的数据集
# load_iris 是一个函数，用于加载鸢尾花数据集。
from sklearn.datasets import load_iris
# 前面说过，机器学习中的个体叫作样本（sample），其属性叫作特征（feature）。
# data数组的形状（shape）是样本数乘以特征数。这是scikit-learn中的约定，你的数据形状应始终遵循这个约定。
#
import numpy as np

# train_test_split函数利用伪随机数生成器将数据集打乱
from sklearn.model_selection import train_test_split
# pd.DataFrame 是 pandas 中用于创建数据框的类。数据框是一个二维表格结构，类似于 Excel 中的表格。
import pandas as pd
import mglearn
def test_split():
    iris_dataset = load_iris()
    # iris_dataset['data'] :这是鸢尾花数据集中的特征数据，包含每个样本的4个特征（花萼长度、花萼宽度、花瓣长度、花瓣宽度
    # iris_dataset['target'] :这是鸢尾花数据集中的目标数据，包含每个样本的目标类别（0、1、2，分别代表 Setosa、Versicolor、Virginica）
    # random_state=0 :这是随机种子参数，用于确保数据划分的结果是可复现的。设置为0（或其他固定值）可以确保每次运行代码时划分的结果相同。
    # train_test_split : 这是 scikit-learn 库中的一个函数，用于将数据集划分为训练集和测试集。
    # 默认情况下，它将数据集划分为75%的训练集和25%的测试集，但可以通过 test_size 参数自定义比例。
    # X_train：训练集的特征数据。
    # X_test：测试集的特征数据。
    # y_train：训练集的目标数据。
    # y_test：测试集的目标数据。
    X_train, X_test, y_train, y_test = train_test_split(iris_dataset['data'],
                                                        iris_dataset['target'],
                                                        random_state=0)
    # 打印训练集和测试集的大小
    print("X_train shape:", X_train.shape)  # 训练集特征数据的形状
    print("X_test shape:", X_test.shape)  # 测试集特征数据的形状
    print("y_train shape:", y_train.shape)  # 训练集目标数据的形状
    print("y_test shape:", y_test.shape)  # 测试集目标数据的形状
    # pandas有一个绘制散点图矩阵的函数，叫作scatter_matrix
    # columns=iris_dataset.feature_names：
    # columns 参数用于指定数据框的列名。iris_dataset.feature_names 是鸢尾花数据集中特征的名称列表。
    iris_dataframe = pd.DataFrame(X_train, columns=iris_dataset.feature_names)
    from pandas.plotting import scatter_matrix
    # pd.scatter_matrix：这是 pandas 库中的一个函数，用于生成散点图矩阵，从 Pandas 0.23.0 版本开始，scatter_matrix 已经被移到 pandas.plotting.scatter_matrix
    # iris_dataframe：包含了训练集的特征数据。
    # figsize=(15, 15)：设置生成的图形的大小为 15x15 英寸。figsize 参数接受一个元组，分别表示图形的宽度和高度（单位为英寸）。
    # marker='o'：设置散点的形状为圆形。marker 参数可以接受多种形状，如 'o'（圆形）、's'（正方形）、'D'（菱形）等。
    # hist_kwds={'bins': 20}：设置直方图的参数。hist_kwds 参数是一个字典，用于传递额外的关键字参数给直方图。这里设置了 bins=20，表示直方图的柱数为 20。
    # s=60：设置散点的大小。s 参数表示散点的面积大小。
    # alpha=.8 ：透明度0.8
    # cmap=mglearn.cm3：设置颜色映射（colormap）
    grr = scatter_matrix(iris_dataframe, c=y_train, figsize=(15, 15),
                            marker='o', hist_kwds={'bins': 20}, s=60, alpha=.8,
                            cmap=mglearn.cm3)
    # 方法可以显示生成的散点图矩阵。
    import matplotlib.pyplot as plt
    plt.show()

# k近邻算法:我们可以考虑训练集中与新数据点最近的任意k个邻居
# （比如说，距离最近的3个或5个邻居），而不是只考虑最近的那一个。然后，我们可以用这些邻居中数量最多的类别做出预测。
def nearest_demo():
    from sklearn.neighbors import KNeighborsClassifier
    # KNeighborsClassifier最重要的参数就是邻居的数目，这里我们设为1：
    knn = KNeighborsClassifier(n_neighbors=1)
    iris_dataset = load_iris()
    X_train, X_test, y_train, y_test = train_test_split(iris_dataset['data'],
                                                        iris_dataset['target'],
                                                        random_state=0)
    # fit 方法是 scikit-learn 中所有模型类的通用方法，用于根据训练数据拟合模型。对于 K近邻算法，fit 方法主要执行以下操作：
    # 存储训练数据 X_train 和目标标签 y_train。
    # 对于 K近邻分类器，fit 方法还会计算训练数据的类别分布。
    # 对于 K近邻回归器，fit 方法会存储目标值 y_train
    knn.fit(X_train, y_train)
    # algorithm：指定用于计算最近邻的算法。
    # 取值：
    #   'auto'：自动选择最合适的算法（默认值）。
    #   'ball_tree'：使用 Ball Tree 算法。
    #   'kd_tree'：使用 KD Tree 算法。
    #   'brute'：使用暴力搜索算法。
    # leaf_size：当使用 'ball_tree' 或 'kd_tree' 算法时，叶节点的大小。
    # metric：用于计算距离的度量方法。
    #   'minkowski'：闵可夫斯基距离（默认值）。当 p=2 时，等同于欧几里得距离；当 p=1 时，等同于曼哈顿距离。
    #   'euclidean'：欧几里得距离。
    #   'manhattan'：曼哈顿距离。
    # metric_params：传递给距离度量函数的额外参数。
    # n_jobs：用于并行计算的CPU核心数。
    # n_neighbors：用于分类的邻居数。
    # p:当 metric='minkowski' 时，用于指定闵可夫斯基距离的参数。
    #   1：曼哈顿距离。
    #   2：欧几里得距离（默认值）。
    # weights：在预测时，邻居的权重函数。
    #   'uniform'：所有邻居的权重相同（默认值）。
    #   'distance'：邻居的权重与其距离成反比。
    KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
                         metric_params=None, n_jobs=1, n_neighbors=1, p=2,
                         weights='uniform')
    # 我们在野外发现了一朵鸢尾花，花萼长5cm宽2.9cm，花瓣长1cm宽0.2cm。这朵鸢尾花属于哪个品种
    X_new = np.array([[5, 2.9, 1, 0.2]])
    # 数组形状为样本数（1）乘以特征数（4）
    print("X_new.shape: {}".format(X_new.shape))
    # 我们调用knn对象的predict方法来进行预测：
    prediction = knn.predict(X_new)
    print("Prediction: {}".format(prediction))
    print("Predicted target name: {}".format(
        iris_dataset['target_names'][prediction]))
    # 评估模型，以上发现的预估是：这朵新的鸢尾花属于类别0，也就是说它属于setosa品种。需要我们评估模型的准确性
    # 我们可以通过计算精度（accuracy）来衡量模型的优劣，精度就是品种预测正确的花所占的比例：
    y_pred = knn.predict(X_test)
    print("Test set predictions:\n {}".format(y_pred))
    # 计算布尔数组的平均值，即准确率。True 被视为 1，False 被视为 0，因此平均值表示预测正确的比例。
    print("Test set score: {:.2f}".format(np.mean(y_pred == y_test)))
    # score 方法计算模型在测试集上的准确率，返回一个浮点数，表示预测正确的比例。
    print("Test set score: {:.2f}".format(knn.score(X_test, y_test)))


def selfDefine():
    iris_dataset = load_iris()
    # 输出的数据集：
    # dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename', 'data_module'])
    print("Keys of iris_dataset: \n{}".format(iris_dataset.keys()))
    # DESCR 是数据集中的一个键，它包含数据集的描述信息，通常是一个字符串。
    # 这是 Python 的切片操作，表示从字符串的开头开始，截取前 193 个字符。
    # 在截取的字符串后面添加换行符 \n 和省略号 ...，表示描述信息被截断了。
    print(iris_dataset['DESCR'][:193] + "\n...")
    # Target names: ['setosa' 'versicolor' 'virginica']
    # target_names键对应的值是一个字符串数组，里面包含我们要预测的花的品种：
    print("Target names: {}".format(iris_dataset['target_names']))
    # feature_names键对应的值是一个字符串列表，对每一个特征进行了说明：
    print("Feature names: \n{}".format(iris_dataset['feature_names']))
    # 数据包含在target和data字段中。data里面是花萼长度、花萼宽度、花瓣长度、花瓣宽度的测量数据，格式为NumPy数组：
    print("Type of data: {}".format(type(iris_dataset['data'])))
    # data数组的每一行对应一朵花，列代表每朵花的四个测量数据：
    print("Shape of data: {}".format(iris_dataset['data'].shape))
    # 下面给出前5个样本的特征数值：
    #
    # [[5.1 3.5 1.4 0.2]
    #  [4.9 3.  1.4 0.2]
    #  [4.7 3.2 1.3 0.2]
    #  [4.6 3.1 1.5 0.2]
    #  [5.  3.6 1.4 0.2]]
    print("First five rows of data:\n{}".format(iris_dataset['data'][:5]))
    # target数组包含的是测量过的每朵花的品种，也是一个NumPy数组：
    print("Type of target: {}".format(type(iris_dataset['target'])))
    # target是一维数组，每朵花对应其中一个数据：
    print("Shape of target: {}".format(iris_dataset['target'].shape))
    # 品种被转换成从0到2的整数：
    # 0代表setosa，1代表versicolor，2代表virginica。代表不同的鸢尾花品种
    print("Target:\n{}".format(iris_dataset['target']))

if __name__ == '__main__':
    nearest_demo()
